package io.gitee.caoxiaoyu97.mybatis;

import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

@Intercepts(
        {
                @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
                @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
                @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        }
)
public class EncryptParameterInterceptor implements Interceptor {

    private List<String> encryptFieldNames = new ArrayList<>();
    // 添加字段缓存
    private Map<Class<?>, List<Field>> fieldCache = new ConcurrentHashMap<>();
    // 添加字段缓存Map
    private Map<String, Field> exampleFieldCache = new ConcurrentHashMap<>();
    // 添加加密结果缓存
    private Map<String, String> encryptCache = new ConcurrentHashMap<>();



    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object arg = args[1];
        String paramName = null;
        if (arg != null && arg instanceof String){
            BoundSql boundSql = ms.getBoundSql(arg);  // 使用 args[1] 作为参数
            List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
            if (parameterMappings != null && !parameterMappings.isEmpty()) {
                paramName = parameterMappings.get(0).getProperty();
            }
        }
        args[1] = doHandle(paramName, args[1],new HashSet<Object>());
        return invocation.getMethod().invoke(invocation.getTarget(), args);
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // TODO
        encryptFieldNames.addAll(Arrays.asList(properties.getProperty("encryptFieldNames").split(",")));
        encryptFieldNames.add("secuNo");
    }

    private boolean shouldEncryptField(String fieldName) {
        return encryptFieldNames.contains(fieldName);
    }

    private String doenc(String fileName, String o1, boolean isLike) {
        // TODO
        // 简单的缓存键生成
        String cacheKey = fileName + ":" + o1 + ":" + isLike;

        return encryptCache.computeIfAbsent(cacheKey, k -> {
            // 这里放置真实的加密逻辑
            return "encrypted_" + o1; // 示例实现
        });
    }


    private Object doHandle(String paramName, Object parameterObject, Set<Object> visited) {

        try {
            if (parameterObject == null) {
                return parameterObject;
            }
            if (visited.contains(parameterObject)){
                return parameterObject;
            }
            visited.add(parameterObject);
            if (parameterObject instanceof String) {
                return doByString(paramName, (String) parameterObject);
            }
            if (parameterObject instanceof Map) {
                Map param = (Map) parameterObject;
                // 获取所有key的副本并转换为String类型
                List<String> keyList = new ArrayList<>();
                param.keySet().forEach(key -> keyList.add(String.valueOf(key)));
                keyList.removeIf(key -> key.matches("param\\d+"));
                for (String key : keyList) {
                    param.put(key, doHandle(key, param.get(key), visited));
                }
            }
            if (parameterObject instanceof List) {
                List param = (List) parameterObject;
                for (int i = 0; i < param.size(); i++) {
                    param.set(i, doHandle(paramName, param.get(i), visited));
                }
            }
            if (!isNotJavaBuiltIn(parameterObject)) {
                return parameterObject;
            }
            if (parameterObject.getClass().getSimpleName().endsWith("Example")) {
                return doByExample(parameterObject);
            }
            // 遍历对象的所有字段，包括父类字段
            Class<?> clazz = parameterObject.getClass();
            List<Field> allFields = getAllFields(clazz); // 使用缓存

            for (Field field : allFields) {
                field.setAccessible(true);
                Object value = field.get(parameterObject);

                if (value instanceof String) {
                    if (shouldEncryptField(field.getName())) {
                        String encryptedValue = doByString(field.getName(), (String) value);
                        field.set(parameterObject, encryptedValue);
                    }
                }
                doHandle(field.getName(), value, visited);
            }

            return parameterObject;
        } catch (Exception e) {
            e.printStackTrace();
            return parameterObject;
        }
    }


    private String doByString(String paramName, String param) {
        return doenc(paramName, param, param.startsWith("%") || param.endsWith("%"));
    }

    private Object doByExample(Object parameterObject) {
        try {
            if (parameterObject == null) {
                return parameterObject;
            }
            Class<?> aClass = parameterObject.getClass();
            if (!parameterObject.getClass().getSimpleName().endsWith("Example")) {
                return parameterObject;
            }
            Field oredCriteriaFiled = getFieldCached(aClass, "oredCriteria");
            // ... 后续代码中的字段获取也使用缓存
            if (oredCriteriaFiled == null) {
                return parameterObject;
            }
            oredCriteriaFiled.setAccessible(true);
            List<Object> oredCriteria = (List<Object>) oredCriteriaFiled.get(parameterObject);
            if (oredCriteria == null) {
                return parameterObject;
            }
            for (Object oredCriterion : oredCriteria) {
                Class<?> aClass1 = oredCriterion.getClass();
                Field condition = getFieldCached(aClass1, "condition");

                if (condition == null) {
                    return parameterObject;
                }
                condition.setAccessible(true);
                String conditionValue = (String) condition.get(oredCriterion);
                String fileName = null;
                for (String encryptFieldName : encryptFieldNames) {
                    if (conditionValue.contains(encryptFieldName + " ")) {
                        fileName = encryptFieldName;
                        break;
                    }
                }
                if (fileName == null) {
                    return parameterObject;
                }
                boolean isLike = conditionValue.contains(" like");
                for (String s : Arrays.asList("value", "secondValue")) {
                    Field value1 = getFieldCached(aClass1, s);
                    value1.setAccessible(true);
                    Object o = value1.get(oredCriterion);
                    if (o != null) {
                        if (o instanceof String) {
                            String encVal = doenc(fileName, (String) o, isLike);
                            value1.set(oredCriterion, encVal);
                        } else if (o instanceof List) {
                            for (int i = 0; i < ((List) o).size(); i++) {
                                Object o1 = ((List) o).get(i);
                                if (o1 instanceof String) {
                                    String encVal = doenc(fileName, (String) o1, isLike);
                                    ((List) o).set(i, encVal);
                                }
                            }
                        }
                    }
                }
            }
            return parameterObject;
        } catch (Exception e) {
            e.printStackTrace();
            return parameterObject;
        }
    }
    public boolean isNotJavaBuiltIn(Object obj) {
        if (obj == null)
            return false;

        // 检查是否为基本类型或其包装类
        Class<?> clazz = obj.getClass();
        return !clazz.isPrimitive() &&
                !isWrapperType(clazz) &&
                !(obj instanceof String) &&
                !(obj instanceof Collection) &&
                !(obj instanceof Map);
    }

    private boolean isWrapperType(Class<?> clazz) {
        return clazz.equals(Boolean.class) ||
                clazz.equals(Integer.class) ||
                clazz.equals(Character.class) ||
                clazz.equals(Byte.class) ||
                clazz.equals(Short.class) ||
                clazz.equals(Double.class) ||
                clazz.equals(Long.class) ||
                clazz.equals(Float.class);
    }
    // 新增方法：获取并缓存类的所有字段
    private List<Field> getAllFields(Class<?> clazz) {
        return fieldCache.computeIfAbsent(clazz, k -> {
            List<Field> fields = new ArrayList<>();
            Class<?> current = k;
            while (current != null && current != Object.class) {
                fields.addAll(Arrays.asList(current.getDeclaredFields()));
                current = current.getSuperclass();
            }
            return fields;
        });
    }
    // 新增方法：带缓存的字段获取
    private Field getFieldCached(Class<?> clazz, String fieldName) throws NoSuchFieldException {
        String key = clazz.getName() + "#" + fieldName;
        return exampleFieldCache.computeIfAbsent(key, k -> {
            try {
                Field field = clazz.getDeclaredField(fieldName);
                field.setAccessible(true);
                return field;
            } catch (NoSuchFieldException e) {
                throw new RuntimeException(e);
            }
        });
    }
}